package org.shagin.jfin.sheet;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.shagin.jfin.core.Utils;
import org.shagin.jfin.core.CoreUtils;
import org.shagin.jfin.data.model.Account;
import org.shagin.jfin.data.model.IBudget;
import org.shagin.jfin.data.model.OperationRule;
import org.shagin.jfin.data.model.OperationRuleAction;
import org.shagin.jfin.data.model.TransferRule;
import org.shagin.jfin.date.DateUtils;
import org.shagin.jfin.sheet.Sheet.CostCenterColumnSetEx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The Class Processor.
 */
public class Processor {
    private static final Logger log = LoggerFactory.getLogger(Processor.class);
    private IBudget budget;
 
    private static SimpleDateFormat fmt = DateUtils.getSimpleDateFormat();

    /**
     * Instantiates a new processor.
     *
     * @param dataSource the data source
     * @param dateFrom the date from
     * @param dateTo the date to
     */
    public Processor(IBudget budget) {
        CoreUtils.checkNotNull(budget, "budget");
        this.budget = budget;
    }

    /**
     * Gets the rule amount.
     *
     * @param rule the rule
     * @param date the date
     * @return the rule amount
     */
    private static double getRuleAmount(OperationRule rule, Date date) {
        double result = 0d;
        for (OperationRuleAction ruleAction : rule.getActions()) {
            if (ruleAction.getDateCondition().validDate(date))
                result += ruleAction.getMoney().getValue();
        }
        return result;
    }

    /**
     * Handle transfres.
     *
     * @param model the model
     * @param row the row
     * @param date the date
     */
    private void handleTransfres(Sheet model, SheetRow row, Date date) {
        for (OperationRule rule : budget.getOperationRuleDomain()
                .getTrasfers()) {

            TransferRule transferRule = (TransferRule) rule;
            CostCenterColumnSetEx colSrc = getCostCenterColumnSet(model,
                    rule.getSourceAccount());
            CostCenterColumnSetEx colDst = getCostCenterColumnSet(model,
                    transferRule.getDestAccount());

            SheetColumnMoney accSrcCol = getColumn(colSrc,
                    rule.getSourceAccount());
            SheetColumnMoney accDstCol = getColumn(colDst,
                    transferRule.getDestAccount());

            double amount = getRuleAmount(rule, date);

            setColumnDummy(row, accSrcCol, -1, amount, true);
            setColumnDummy(row, accDstCol, 1, amount, true);

            if (colSrc != colDst) {
                setColumnDummy(row, colSrc.colExpense, 1, amount, false);
                setColumnDummy(row, colDst.colIncome, 1, amount, false);
            }
        }
    }

    /**
     * Sets the column dummy.
     *
     * @param row the row
     * @param col the col
     * @param sign the sign
     * @param amount the amount
     * @param dummy the dummy
     */
    private void setColumnDummy(SheetRow row, SheetColumnMoney col, int sign,
            double amount, boolean dummy) {
        double v = row.getDouble(col);
        if (dummy)
            v = col.getDummy();

        v = v + sign * amount;

        if (dummy)
            col.setDummy(v);

        if (!Utils.isZero(v))
            row.setDouble(col, v);
    }

    /**
     * Handle operation rules.
     *
     * @param model the model
     * @param row the row
     * @param totalCol the total col
     * @param cols the cols
     * @param date the date
     * @param sign the sign
     */
    private void handleOperationRules(Sheet model, SheetRow row,
            SheetColumnMoney totalCol, List<SheetColumn<?>> cols, Date date,
            int sign) {
        double total = 0;
        for (SheetColumn<?> col : cols) {
            if (col instanceof SheetColumnOperationRule) {
                SheetColumnOperationRule opCol = (SheetColumnOperationRule) col;
                List<OperationRule> rules = opCol.getRules();
                int value = 0;
                for (OperationRule rule : rules) {
                    double v = getRuleAmount(rule, date);
                    if (!Utils.isZero(v)) {
                        value += v;
                        processOperation(model, row, rule.getSourceAccount(),
                                v, sign);
                    }
                }

                if (!Utils.isZero(value))
                    row.setDouble(col, value);

                total += value;
            }
        }
        if (!Utils.isZero(total))
            row.setDouble(totalCol, new Double(total));
    }

    /**
     * Gets the column.
     *
     * @param ccc the ccc
     * @param account the account
     * @return the column
     */
    private static SheetColumnMoney getColumn(CostCenterColumnSetEx ccc,
            Account account) {
        SheetColumnMoney result = null;
        for (Map.Entry<Account, SheetColumnMoney> es : ccc.accounts.entrySet()) {
            if (es.getKey().getId().equalsIgnoreCase(account.getId())) {
                result = es.getValue();
                break;
            }
        }

        return result;
    }

    /**
     * Gets the cost center column set.
     *
     * @param model the model
     * @param account the account
     * @return the cost center column set
     */
    private static CostCenterColumnSetEx getCostCenterColumnSet(Sheet model,
            Account account) {
        CostCenterColumnSetEx result = null;
        for (CostCenterColumnSetEx ccc : model.getCostCenters()) {
            for (Account a : ccc.accounts.keySet()) {
                if (a.getId().equalsIgnoreCase(account.getId())) {
                    result = ccc;
                    break;
                }
            }
            if (result != null)
                break;
        }
        return result;
    }

    /**
     * Process operation.
     *
     * @param model the model
     * @param row the row
     * @param account the account
     * @param amount the amount
     * @param sign the sign
     */
    private void processOperation(Sheet model, SheetRow row, Account account,
            double amount, int sign) {
        CostCenterColumnSetEx ccCol = getCostCenterColumnSet(model, account);
        SheetColumnMoney accCol = getColumn(ccCol, account);

        double v = accCol.getDummy();
        v = v + sign * amount;
        accCol.setDummy(v);
        row.setDouble(accCol, accCol.getDummy());

        SheetColumnMoney col = (sign == -1 ? ccCol.colExpense : ccCol.colIncome);
        v = row.getDouble(col);
        v += amount;
        col.setDummy(v);
        row.setDouble(col, v);
    }

    /**
     * Handle sub total rows.
     *
     * @param model the model
     * @param aggrRows the aggr rows
     */
    private void handleSubTotalRows(Sheet model, List<SheetRow> aggrRows) {
        for (SheetRow aggrRow : aggrRows) {
            SheetRowSubTotal rowSubTotal = (SheetRowSubTotal) aggrRow;
            handleSubTotalRows(model, rowSubTotal);
        }
    }

    /**
     * Handle sub total rows.
     *
     * @param sheet the sheet
     * @param rowSubTotal the row sub total
     */
    private void handleSubTotalRows(Sheet sheet, SheetRowSubTotal rowSubTotal) {
        for (SheetColumn<?> col : sheet.getData().getColumns()) {
            if (col instanceof SheetColumnDouble) {
                Double totalValue = rowSubTotal.getDouble(col);

                for (SheetRow row : rowSubTotal.getRows())
                    totalValue += row.getDouble(col);

                if (!Utils.isZero(totalValue)
                        && (!sheet.totalSaldo.colSaldo.equals(col)))
                    rowSubTotal.setDouble(col, totalValue);

            }
        }
    }

    /**
     * Handle sub total col.
     *
     * @param model the model
     */
    private void handleSubTotalCol(Sheet model) {
        for (SheetColumnCategory cat : model.getSheetColumnCategories())
            handleSubTotalCol(model, cat);

    }

    /**
     * Handle sub total col.
     *
     * @param sheet the sheet
     * @param colCategory the col category
     */
    private void handleSubTotalCol(Sheet sheet, SheetColumnCategory colCategory) {
        List<SheetColumnOperationRule> cols = colCategory.getNestedCols(sheet);
        for (SheetRow row : sheet.getRows()) {
            Double total = 0d;
            for (SheetColumnOperationRule col : cols)
                total += row.getDouble(col);

            if (!Utils.isZero(total))
                row.setDouble(colCategory, total);
        }
    }

    /**
     * Execute.
     *
     * @return the sheet
     */
    public Sheet execute() {
        log.trace("enter execute()");
        final Sheet result = new Sheet();
        result.setupColumns(budget);
        result.totalSaldo.colSaldo.setDummy(budget.totalAmount());

        for (CostCenterColumnSetEx ccc : result.getCostCenters()) {
            for (Map.Entry<Account, SheetColumnMoney> es : ccc.accounts
                    .entrySet()) {
                es.getValue().setDummy(es.getKey().getAmount().getValue());
            }
            ccc.colSaldo.setDummy(ccc.costCenter.getAmount(
                    budget.getDefaultCurrency()).getValue());
        }

        SheetRowGroup rowGroup = new SheetRowGroup(budget.getDateRange());
        SheetRowSubTotal aggrRow = result.createRowSubTotal();
        Date date = DateUtils.yesterday(budget.getDateRange().getStart());
        do {
            date = DateUtils.tomorrow(date);

            SheetRowData row = result.createRow();
            row.setString(result.colLabel, fmt.format(date));
            row.setTime(date);

            handleOperationRules(result, row, result.totalSaldo.colIncome,
                    result.getIncomeSection().getColumns(), date, 1);
            handleOperationRules(result, row, result.totalSaldo.colExpense,
                    result.getExpenseSection().getColumns(), date, -1);
            handleTransfres(result, row, date);

            result.totalSaldo.updateSaldo(row);

            for (CostCenterColumnSetEx ccc : result.getCostCenters()) {
                for (Map.Entry<Account, SheetColumnMoney> es : ccc.accounts
                        .entrySet()) {
                    if (!Utils.isZero(es.getValue().getDummy()))
                        row.setDouble(es.getValue(), es.getValue().getDummy());
                }
                ccc.updateSaldo(row);
            }

            result.addRow(row);
            aggrRow.addRow(row);

            if (rowGroup.isGroupEnd(row.getTime())) {
                aggrRow.setString(result.colLabel,
                        rowGroup.getLabel(row.getTime()));
                result.addSubTotalRow(aggrRow);
                SheetRowTotalPercent percentRow = aggrRow
                        .createPercentRow(result.colLabel);
                result.addRow(percentRow);
                result.addTotalPercentRow(percentRow);

                aggrRow = result.createRowSubTotal();
            }

        } while (!rowGroup.isEnd(date));

        handleSubTotalCol(result);

        SheetRowTotal rowTotal = result.createRowTotal();
        SheetRowTotalPercent percentRowTotal = rowTotal
                .createPercentRow(result.colLabel);

        result.addRow(rowTotal);
        result.addRow(percentRowTotal);
        result.addTotalPercentRow(percentRowTotal);

        handleSubTotalRows(result, result.getAggrRows());
        handleSubTotalRows(result, rowTotal);

        for (SheetRowTotalPercent row : result.getDataPercent())
            row.update(result);

        return result;
    }

}
